package bancosys.tec.utils.db.descriptor;

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * Usado para fazer flatten de um grafo de descritores de base de referência.
 * 
 * @author jrenaut
 */
public final class FlatReferenceDatabaseBuilder {

    /**
     * Retorna a lista de entradas que devem ser importadas em ordem para representar os descritores. Recupera apenas as entradas do tipo
     * dado.
     * 
     * @param descriptions ReferenceDatabaseDescription[]
     * @param type o tipo
     * @return List
     */
    public static List<ReferenceDatabaseEntry> flatten(List<ReferenceDatabaseDescription> descriptions,
            ReferenceDatabaseDescriptionType type) {
        return new FlatReferenceDatabaseBuilder().flattenDescriptions(descriptions, type);
    }

    private Set<String> visited = new HashSet<String>();

    /**
     * C'tor
     */
    private FlatReferenceDatabaseBuilder() {
    }

    /**
     * Retorna a lista flat
     * 
     * @param descriptions Descritores
     * @param type O tipo
     * @return List
     */
    public List<ReferenceDatabaseEntry> flattenDescriptions(List<ReferenceDatabaseDescription> descriptions,
            ReferenceDatabaseDescriptionType type) {
        List<ReferenceDatabaseEntry> list = new LinkedList<ReferenceDatabaseEntry>();
        for (ReferenceDatabaseDescription referenceDatabaseDescription : descriptions) {
            list.addAll(this.visit(referenceDatabaseDescription, type));
        }
        return list;
    }

    /**
     * Retorna a lista flat de um descritor e suas dependências
     * 
     * @param referenceDatabaseDescription Descritor
     * @param type O tipo
     * @return List
     */
    private List<ReferenceDatabaseEntry> visit(ReferenceDatabaseDescription referenceDatabaseDescription,
            ReferenceDatabaseDescriptionType type) {
        // verifica que este descritor ja nao foi visitado
        if (!this.visited.contains(referenceDatabaseDescription.getIdentifier())) {
            this.visited.add(referenceDatabaseDescription.getIdentifier());

            List<ReferenceDatabaseEntry> list = new LinkedList<ReferenceDatabaseEntry>();
            for (ReferenceDatabaseDescription dependencyDescription : referenceDatabaseDescription.getDependencies()) {
                list.addAll(this.visit(dependencyDescription, type));
            }
            switch (type) {
            case SCHEMA:
                list.addAll(referenceDatabaseDescription.getSchemaEntries());
                break;
            case POSTSCRIPT:
                list.addAll(referenceDatabaseDescription.getPostEntries());
                break;
            case REFERENCE:
                list.addAll(referenceDatabaseDescription.getReferenceEntries());
                break;
            default:
                break;
            }
            return list;
        }
        return Collections.emptyList();
    }

    /**
     * Enum para diferenciar os tipos de entradas.
     * 
     * @author jrenaut
     */
    public static enum ReferenceDatabaseDescriptionType {
        SCHEMA {
            /**
             * {@inheritDoc}
             */
            @Override
            public ReferenceDatabaseDescriptionType[] getActions() {
                return new ReferenceDatabaseDescriptionType[]{ SCHEMA };
            }
        },

        POSTSCRIPT {
            /**
             * {@inheritDoc}
             */
            @Override
            public ReferenceDatabaseDescriptionType[] getActions() {
                return new ReferenceDatabaseDescriptionType[]{ SCHEMA, POSTSCRIPT };
            }
        },

        REFERENCE {
            /**
             * {@inheritDoc}
             */
            @Override
            public ReferenceDatabaseDescriptionType[] getActions() {
                return new ReferenceDatabaseDescriptionType[]{ SCHEMA, POSTSCRIPT, REFERENCE };
            }
        },

        ALL {
            /**
             * {@inheritDoc}
             */
            @Override
            public ReferenceDatabaseDescriptionType[] getActions() {
                return new ReferenceDatabaseDescriptionType[]{ SCHEMA, POSTSCRIPT, REFERENCE };
            }
        };

        /**
         * Retorna as acoes que devem ser executadas para montar a base de dados.
         * 
         * @return as acoes que devem ser executadas para montar a base de dados
         */
        public abstract ReferenceDatabaseDescriptionType[] getActions();
    }

}
